package com.emeraldparser.util;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Iterators;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Table;
import com.google.common.collect.Tables;

public abstract class AbstractMultitable<R, C, V> implements Multitable<R, C, V> {

	private transient Table<R, C, Collection<V>> table;
	private transient int totalSize;

	protected AbstractMultitable(Table<R, C, Collection<V>> table) {
		checkArgument(table.isEmpty());
		this.table = table;
	}

	/** Used during deserialization only. */
	final void setTable(Table<R, C, Collection<V>> table) {
		this.table = table;
		this.totalSize = 0;
		for (Collection<V> values : table.values()) {
			checkArgument(!values.isEmpty());
			totalSize += values.size();
		}
	}

	abstract Collection<V> createCollection();

	Collection<V> createCollection(R rowKey, C columnKey) {
		return createCollection();
	}

	Table<R, C, Collection<V>> backingTable() {
		return table;
	}

	// Query Operations

	@Override public int size() {
		return totalSize;
	}

	@Override public boolean isEmpty() {
		return totalSize == 0;
	}

	@Override public boolean contains(Object rowKey, Object columnKey) {
		return table.contains(rowKey, columnKey);
	}

	@Override public boolean containsRow(Object rowKey) {
		return table.containsRow(rowKey);
	}

	@Override public boolean containsColumn(Object columnKey) {
		return table.containsColumn(columnKey);
	}

	@Override public boolean containsValue(Object value) {
		for (Collection<V> collection : table.values()) {
			if (collection.contains(value)) {
				return true;
			}
		}
		
		return false;
	}

	/**
	 * TODO Rename to "containsCell"?
	 * @param rowKey
	 * @param columnKey
	 * @param value
	 * @return
	 */
	public boolean containsEntry(Object rowKey, Object columnKey, Object value) {
		Collection<V> collection = table.get(rowKey, columnKey);
		return collection != null && collection.contains(value);
	}

	@Override public boolean put(R rowKey, C columnKey, V value) {
		Collection<V> collection = getOrCreateCollection(rowKey, columnKey);
		
		if (collection.add(value)) {
			totalSize++;
			return true;
		} else {
			return false;
		}
	}

	private Collection<V> getOrCreateCollection(R rowKey, C columnKey) {
		Collection<V> collection = table.get(rowKey, columnKey);
		if (collection == null) {
			collection = createCollection(rowKey, columnKey);
			table.put(rowKey, columnKey, collection);
		}
		return collection;
	}

	@Override public boolean remove(Object rowKey, Object columnKey, Object value) {
		Collection<V> collection = table.get(rowKey, columnKey);
		if (collection == null) {
			return false;
		}
		
		boolean changed = collection.remove(value);
		if (changed) {
			totalSize--;
			if (collection.isEmpty()) {
				table.remove(rowKey, columnKey);
			}
		}
		return changed;
	}

	@Override public boolean putAll(R rowKey, C columnKey, Iterable<? extends V> values) {
		if (!values.iterator().hasNext()) {
			return false;
		}
		Collection<V> collection = getOrCreateCollection(rowKey, columnKey);
		int oldSize = collection.size();
		
		boolean changed = false;
		if (values instanceof Collection) {
			Collection<? extends V> c = (Collection<? extends V>) values;
			changed = collection.addAll(c);
		} else {
			for (V value : values) {
				changed |= collection.add(value);
			}
		}
		
		totalSize += (collection.size() - oldSize);
		return changed;
	}

	@Override public boolean putAll(Multitable<? extends R, ? extends C, ? extends V> multitable) {
		boolean changed = false;
		for (Table.Cell<? extends R, ? extends C, ? extends V> cell : multitable.cells()) {
			changed |= put(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
		}
		return changed;
	}

	/**
	 * {@inheritDoc}}
	 * 
	 * @param rowKey
	 * @param columnKey
	 * @param values
	 * @return
	 */
	public Collection<V> replaceValues(R rowKey, C columnKey, Iterable<? extends V> values) {
		Iterator<? extends V> iterator = values.iterator();
		if (!iterator.hasNext()) {
			return removeAll(rowKey, columnKey);
		}
		
		Collection<V> collection = getOrCreateCollection(rowKey, columnKey);
		Collection<V> oldValues = createCollection();
		oldValues.addAll(collection);
		
		totalSize -= collection.size();
		collection.clear();
		
		while (iterator.hasNext()) {
			if (collection.add(iterator.next())) {
				totalSize++;
			}
		}
		
		return unmodifiableCollectionSubclass(oldValues);
	}

	@Override public Collection<V> removeAll(Object rowKey, Object columnKey) {
		Collection<V> collection = table.remove(rowKey, columnKey);
		Collection<V> output = createCollection();
		
		if (collection != null) {
			output.addAll(collection);
			totalSize -= collection.size();
			collection.clear();
		}
		
		return unmodifiableCollectionSubclass(output);
	}

	private Collection<V> unmodifiableCollectionSubclass(Collection<V> collection) {
		// TODO
		return null;
	}

	@Override public void clear() {
		for (Collection<V> collection : table.values()) {
			collection.clear();
		}
		table.clear();
		totalSize = 0;
	}

	// Views

	public Collection<V> get(R rowKey, C columnKey) {
		Collection<V> collection = table.get(rowKey, columnKey);
		if (collection == null) {
			collection = createCollection(rowKey, columnKey);
		}
		return wrapCollection(rowKey, columnKey, collection);
	}

	private Collection<V> wrapCollection(R rowKey, C columnKey, Collection<V> collection) {
		// TODO
		return null;
	}

	private transient Set<R> rowKeySet;

	@Override public Set<R> rowKeySet() {
		Set<R> result = rowKeySet;
		return (result == null) ? rowKeySet = createRowKeySet() : result;
	}

	private Set<R> createRowKeySet() {
		return new RowKeySet(table);
	}

	private class RowKeySet extends AbstractSet<R> {
		
		final Table<R, C, Collection<V>> subTable;
		
		RowKeySet(final Table<R, C, Collection<V>> subTable) {
			this.subTable = subTable;
		}
		
		@Override public int size() {
			return subTable.size();
		}
		
		@Override public Iterator<R> iterator() {
			return new Iterator<R>() {
				final Iterator<Table.Cell<R, C, Collection<V>>> cellIterator = subTable.cellSet().iterator();
				Table.Cell<R, C, Collection<V>> cell;
				
				@Override
				public boolean hasNext() {
					return cellIterator.hasNext();
				}
				
				@Override
				public R next() {
					cell = cellIterator.next();
					return cell.getRowKey();
				}
				
				@Override
				public void remove() {
					checkState(cell != null);
					Collection<V> collection = cell.getValue();
					cellIterator.remove();
					totalSize -= collection.size();
					collection.clear();
				}
			};
		}
		
		@Override public boolean contains(Object rowKey) {
			return subTable.containsRow(rowKey);
		}
		
		@Override public boolean remove(Object rowKey) {
			throw new UnsupportedOperationException();
			/*
			int count = 0;
			Collection<V> collection = subTable.remove(rowKey, columnKey);
			if (collection != null) {
				count = collection.size();
				collection.clear();
				totalSize -= count;
			}
			return count > 0;
			*/
		}
		
		@Override public boolean removeAll(Collection<?> c) {
			checkNotNull(c);
			return super.removeAll(c);
		}
		
		@Override public boolean containsAll(Collection<?> c) {
			return subTable.rowKeySet().containsAll(c);
		}
		
		@Override public boolean equals(Object object) {
			return this == object || this.subTable.rowKeySet().equals(object);
		}
		
		@Override public int hashCode() {
			return subTable.rowKeySet().hashCode();
		}
	}

	private transient Multiset<R> rowMultiset;

	//@Override
	@Override public Multiset<R> rowKeys() {
		throw new UnsupportedOperationException();
		/*
		Multiset<R> result = rowMultiset;
		return (result == null) ? rowMultiset = new RowMultisetView() : result;
		*/
	}

	/** Row multiset view that stays in sync with the multable keys. */
	private class RowMultisetView extends AbstractMultiset<R> {
		
		@Override public int remove(Object rowKey, int occurrences) {
			throw new UnsupportedOperationException();
			/*
			if (occurrences == 0) {
				return count(rowKey);
			}
			checkArgument(occurrences > 0);
			
			Collection<V> collection;
			try {
				collection = table.get(rowKey);
			}
			*/
		}

		@Override
		public Set<Multiset.Entry<R>> entrySet() {
			throw new UnsupportedOperationException();
		}
		
	}

	private int removeValuesForKey(Object rowKey, Object columnKey) {
		Collection<V> collection;
		try {
			collection = table.remove(rowKey, columnKey);
		} catch (NullPointerException e) {
			return 0;
		} catch (ClassCastException e) {
			return 0;
		}
		
		int count = 0;
		if (collection != null) {
			count = collection.size();
			collection.clear();
			totalSize -= count;
		}
		return count;
	}

	private transient Collection<V> valuesCollection;

	/**
	 * {@inheritDoc}}
	 */
	//@Override
	@Override public Collection<V> values() {
		Collection<V> result = valuesCollection;
		return (result == null) ? valuesCollection = new Values() : result;
	}

	private class Values extends AbstractCollection<V> {
		@Override public Iterator<V> iterator() {
			throw new UnsupportedOperationException();
			//return new ValueIterator();
		}
		@Override public int size() {
			return totalSize;
		}
		
		// The following methods are included to improve performance.
		
		@Override public void clear() {
			AbstractMultitable.this.clear();
		}
		
		@Override public boolean contains(Object value) {
			return containsValue(value);
		}
	}

	/** Iterator across all values. */
	/*
	private class ValueIterator implements Iterator<V> {
		final Iterator<Table.Cell<R, C, V>> cellIterator = createCellIterator();
		
		@Override
		public boolean hasNext() {
			return cellIterator.hasNext();
		}
		@Override
		public V next() {
			return cellIterator.next().getValue();
		}
		@Override
		public void remove() {
			cellIterator.remove();
		}
	}
	*/

	private transient Collection<Table.Cell<R, C, V>> cells;

	@Override
	public Collection<Table.Cell<R, C, V>> cells() {
		Collection<Table.Cell<R, C, V>> result = cells;
		return (result == null) ? cells = createCells() : result;
	}

	private Collection<Table.Cell<R, C, V>> createCells() {
		// TODO
		return new Cells();
	}

	private class Cells extends AbstractCollection<Table.Cell<R, C, V>> {
		@Override public Iterator<Table.Cell<R, C, V>> iterator() {
			return createCellIterator();
		}
		@Override public int size() {
			return totalSize;
		}
		
		// The following methods are included to improve performance.
		
		@Override public boolean contains(Object o) {
			if (!(o instanceof Table.Cell)) {
				return false;
			}
			Table.Cell<?, ?, ?> cell = (Table.Cell<?, ?, ?>) o;
			return containsEntry(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
		}
		
		@Override public void clear() {
			AbstractMultitable.this.clear();
		}
		
		@Override public boolean remove(Object o) {
			if (!(o instanceof Table.Cell)) {
				return false;
			}
			Table.Cell<?, ?, ?> cell = (Table.Cell<?, ?, ?>) o;
			return AbstractMultitable.this.remove(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
		}
	}

	Iterator<Table.Cell<R, C, V>> createCellIterator() {
		return new CellIterator();
	}

	private class CellIterator implements Iterator<Table.Cell<R, C, V>> {
		final Iterator<Table.Cell<R, C, Collection<V>>> keyIterator;
		R rowKey;
		C columnKey;
		Collection<V> collection;
		Iterator<V> valueIterator;
		
		CellIterator() {
			keyIterator = table.cellSet().iterator();
			if (keyIterator.hasNext()) {
				findValueIteratorAndKey();
			} else {
				valueIterator = Iterators.emptyIterator();
			}
		}
		
		void findValueIteratorAndKey() {
			Table.Cell<R, C, Collection<V>> cell = keyIterator.next();
			rowKey = cell.getRowKey();
			columnKey = cell.getColumnKey();
			collection = cell.getValue();
			valueIterator = collection.iterator();
		}
		
		@Override
		public boolean hasNext() {
			return keyIterator.hasNext() || valueIterator.hasNext();
		}
		
		@Override
		public Table.Cell<R, C, V> next() {
			if (!valueIterator.hasNext()) {
				findValueIteratorAndKey();
			}
			return Tables.immutableCell(rowKey, columnKey, valueIterator.next());
		}
		
		@Override
		public void remove() {
			valueIterator.remove();
			if (collection.isEmpty()) {
				keyIterator.remove();
			}
			totalSize--;
		}
		
	}

	private transient Table<R, C, Collection<V>> asTable;

	@Override
	public Table<R, C, ? extends Collection<V>> asTable() {
		Table<R, C, Collection<V>> result = asTable;
		return (result == null) ? asTable = createAsTable() : result;
	}

	private Table<R, C, Collection<V>> createAsTable() {
		return null; // FIXME
	}

	// TODO class AsTable

	@Override public boolean equals(Object object) {
		if (object == this) {
			return true;
		}
		if (object instanceof Multitable) {
			Multitable<?, ?, ?> that = (Multitable<?, ?, ?>) object;
			return this.table.equals(that.asTable());
		}
		return false;
	}

	@Override
	public Multimap<C, V> row(R rowKey) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Multimap<R, V> column(C columnKey) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Set<C> columnKeySet() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Multiset<C> columnKeys() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Map<R, ? extends Multimap<C, V>> rowMap() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Map<C, ? extends Multimap<R, V>> columnMap() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override public int hashCode() {
		return table.hashCode();
	}

	@Override public String toString() {
		return table.toString();
	}

}
